/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.harmony.javax.naming.directory;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import org.apache.harmony.javax.naming.NamingEnumeration;
import org.apache.harmony.jndi.internal.nls.Messages;

/**
 * A simple implementation of the <code>Attributes</code> interface.
 * <p>
 * The <code>BasicAttributes</code> provides operations on any types of
 * attribute. When a new attribute is created the <code>BasicAttributes</code>
 * class will create a new <code>BasicAttribute</code> and add it to the
 * attribute collection.
 * </p>
 * <p>
 * A particular instance of <code>BasicAttributes</code> can be either
 * case-sensitive or case-insensitive, as defined by the <code>isCaseIgnored()
 * </code> method.
 * </p>
 * <p>
 * Note that changes to the <code>BasicAttributes</code> are local -- they do
 * not modify the directory. The directory is only modified by API calls on the
 * {@link DirContext} object.
 * </p>
 * 
 * @see Attributes
 */
public class BasicAttributes implements Attributes {

	/*
	 * This constant is used during deserialization to check the version which
	 * created the serialized object.
	 */
	private static final long serialVersionUID = 0x451d18d6a95539d8L;

	/**
	 * Flag indicating whether the case of attribute identifier is ignored.
	 * 
	 * @serial
	 */
	private final boolean ignoreCase;

	// A map, Id => Attribute
	private transient Hashtable<String, Attribute> attrMap = new Hashtable<String, Attribute>();

	/**
	 * Constructs a <code>BasicAttributes</code> instance which is
	 * case-sensitive.
	 */
	public BasicAttributes() {
		this(false);
	}

	/**
	 * Constructs a <code>BasicAttributes</code> instance which is
	 * case-sensitive if <code>flag</code> is false.
	 * 
	 * @param flag
	 *            Indicates whether this instance is case-insensitive.
	 */
	public BasicAttributes(boolean flag) {
		ignoreCase = flag;
	}

	/**
	 * Constructs a case-sensitive <code>BasicAttributes</code> instance with
	 * one attribute.
	 * 
	 * @param attrId
	 *            the ID of the first attribute
	 * @param attrObj
	 *            the value of the first attribute
	 */
	public BasicAttributes(String attrId, Object attrObj) {
		this(attrId, attrObj, false);
	}

	/**
	 * Constructs a <code>BasicAttributes</code> instance with one attribute
	 * which is case-sensitive if <code>flag</code> is false.
	 * 
	 * @param attrId
	 *            the ID of the first attribute
	 * @param attrObj
	 *            the value of the first attribute
	 * @param flag
	 *            Indicates whether this instance is case-insensitive.
	 */
	public BasicAttributes(String attrId, Object attrObj, boolean flag) {
		ignoreCase = flag;
		attrMap.put(convertId(attrId), new BasicAttribute(attrId, attrObj));
	}

	/**
	 * Returns a deep copy of this attribute collection. The returned copy
	 * contains the same attribute objects. The attribute objects are not
	 * cloned.
	 * 
	 * @return a deep copy of this attribute collection
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Object clone() {
		try {
			final BasicAttributes c = (BasicAttributes) super.clone();
			c.attrMap = (Hashtable<String, Attribute>) attrMap.clone();
			return c;
		} catch (final CloneNotSupportedException e) {
			// jndi.15=Failed to clone object of BasicAttributes class.
			throw new AssertionError(Messages.getString("jndi.15")); //$NON-NLS-1$
		}
	}

	/*
	 * Convert an attribute ID to lower case if this attribute collection is
	 * case-insensitive.
	 */
	private String convertId(String id) {
		return ignoreCase ? id.toLowerCase() : id;
	}

	/**
	 * Returns true if this <code>BasicAttributes</code> instance is equal to
	 * the supplied object <code>obj</code>. They are considered equal if they
	 * handle case the same way and have equal attributes.
	 * <code>Attribute</code> equality is tested by calling <code>
	 * equals</code> on each attribute, which may be overridden.
	 * 
	 * @param obj
	 *            the object to compare with
	 * @return true if this object is equal to <code>obj</code>, otherwise false
	 */
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof Attributes)) {
			return false;
		}

		// compare case & size
		final Attributes o = (Attributes) obj;
		if (isCaseIgnored() != o.isCaseIgnored() || size() != o.size()) {
			return false;
		}

		// compare each attribute
		final Iterator<Map.Entry<String, Attribute>> it = attrMap.entrySet()
				.iterator();
		while (it.hasNext()) {
			final Map.Entry<String, Attribute> e = it.next();
			if (!e.getValue().equals(o.get(e.getKey()))) {
				return false;
			}
		}

		return true;
	}

	@Override
	public Attribute get(String id) {
		return attrMap.get(convertId(id));
	}

	@Override
	public NamingEnumeration<Attribute> getAll() {
		return new BasicNamingEnumeration<Attribute>(attrMap.elements());
	}

	@Override
	public NamingEnumeration<String> getIDs() {
		if (ignoreCase) {
			final Enumeration<Attribute> e = attrMap.elements();
			final Vector<String> v = new Vector<String>(attrMap.size());

			while (e.hasMoreElements()) {
				v.add((e.nextElement()).getID());
			}

			return new BasicNamingEnumeration<String>(v.elements());
		}
		return new BasicNamingEnumeration<String>(attrMap.keys());
	}

	/**
	 * Returns the hashcode for this <code>BasicAttributes</code> instance. The
	 * result is calculated by summing the hashcodes of all attributes,
	 * incremented by one if this instance is not case-sensitive.
	 * 
	 * @return the hashcode of this <code>BasicAttributes</code> instance
	 */
	@Override
	public int hashCode() {
		final Enumeration<Attribute> e = attrMap.elements();
		int i = (ignoreCase ? 1 : 0);

		while (e.hasMoreElements()) {
			i += e.nextElement().hashCode();
		}

		return i;
	}

	@Override
	public boolean isCaseIgnored() {
		return ignoreCase;
	}

	@Override
	public Attribute put(Attribute attribute) {
		final String id = convertId(attribute.getID());
		return attrMap.put(id, attribute);
	}

	@Override
	public Attribute put(String id, Object obj) {
		return put(new BasicAttribute(id, obj));
	}

	/*
	 * Serialization of the <code>BasicAttributes</code> class is as follows:
	 * ignore attribute case (boolean) number of attributes (int) list of
	 * attribute objects
	 */
	private void readObject(ObjectInputStream ois) throws IOException,
			ClassNotFoundException {
		int size;

		ois.defaultReadObject();
		size = ois.readInt();
		attrMap = new Hashtable<String, Attribute>();
		for (int i = 0; i < size; i++) {
			final BasicAttribute attr = (BasicAttribute) ois.readObject();
			attrMap.put(convertId(attr.getID()), attr);
		}
	}

	@Override
	public Attribute remove(String id) {
		return attrMap.remove(convertId(id));
	}

	@Override
	public int size() {
		return attrMap.size();
	}

	/**
	 * Returns the string representation of this <code>BasicAttributes</code>
	 * instance. The result contains the attribute identifiers and values'
	 * string representations.
	 * 
	 * @return the string representation of this object
	 */
	@Override
	public String toString() {
		String s = null;
		final Iterator<Map.Entry<String, Attribute>> it = attrMap.entrySet()
				.iterator();
		Map.Entry<String, Attribute> e = null;

		if (it.hasNext()) {
			// If there are one or more attributes, print them all.
			e = it.next();
			s = "{\n"; //$NON-NLS-1$
			s += e.getKey();
			s += "=" + e.getValue().toString(); //$NON-NLS-1$
			while (it.hasNext()) {
				e = it.next();
				s += "; "; //$NON-NLS-1$
				s += e.getKey();
				s += "=" + e.getValue().toString(); //$NON-NLS-1$
			}
			s += "}\n"; //$NON-NLS-1$
		} else {
			// Otherwise, print an indication that no attributes are stored.
			s = "This Attributes does not have any attributes.\n"; //$NON-NLS-1$
		}
		return s;
	}

	/*
	 * Serialization of the <code>BasicAttributes</code> class is as follows:
	 * ignore attribute case (boolean) number of attributes (int) list of
	 * attribute objects
	 */
	private void writeObject(ObjectOutputStream oos) throws IOException {
		oos.defaultWriteObject();
		oos.writeInt(attrMap.size());
		for (final Enumeration<Attribute> enumeration = attrMap.elements(); enumeration
				.hasMoreElements();) {
			oos.writeObject(enumeration.nextElement());
		}
	}
}
